package com.example;

import com.example.annotation.Autowired;
import com.example.utils.AssertUtils;
import com.example.utils.ClassUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ApplicationContext {

    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();

    public ApplicationContext(Class<?> originSourceClass) {
        BeanDefinitionReader beanDefinitionReader = new BeanDefinitionReader();
        List<BeanDefinition> beanDefinitions = beanDefinitionReader.loadBeanDefinitions(originSourceClass);
        doRegisterBeanDefinition(beanDefinitions);
        doRegisterBeanValue();
    }

    private void doRegisterBeanDefinition(List<BeanDefinition> beanDefinitions) {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
            factoryBeanInstanceCache.put(beanDefinition.getFactoryBeanName(), new BeanWrapper(ClassUtils.newInstance(beanDefinition.getBeanClass()), beanDefinition.getBeanClass()));
        }
    }

    private void doRegisterBeanValue() {
        for (Map.Entry<String, BeanWrapper> entry : factoryBeanInstanceCache.entrySet()) {
            populateBean(entry.getValue());
        }
    }

    private void populateBean(BeanWrapper beanWrapper) {
        Object instance = beanWrapper.getWrapperInstance();
        Class<?> clazz = beanWrapper.getWrapperClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                String message = String.format("class %s field %s is final", clazz.getSimpleName(), field.getName());
                AssertUtils.isTrue(!Modifier.isFinal(field.getModifiers()), message);
                Autowired annotation = field.getAnnotation(Autowired.class);
                String autowiredBeanName = field.getType().getName();
                if (!annotation.value().equals("")) {
                    autowiredBeanName = annotation.value();
                }
                BeanWrapper beanWrapper1 = this.factoryBeanInstanceCache.get(autowiredBeanName);
                String message1 = String.format("class %s field %s unknown source", clazz.getSimpleName(), field.getName());
                AssertUtils.isTrue(beanWrapper1 != null, message1);
                try {
                    field.setAccessible(true);
                    field.set(instance, beanWrapper1.getWrapperInstance());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public Object getBean(Class<?> beanClass) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanClass.getName());
        if (beanDefinition != null) {
            return factoryBeanInstanceCache.get(beanDefinition.getFactoryBeanName());
        }
        return null;
    }

    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    public Map<String, BeanWrapper> getBeanDefinitionForAnnotation(Class<? extends Annotation> classZ) {
        Map<String, BeanWrapper> resultMap = new HashMap<>();
        if (classZ != null && classZ.isAnnotation()) {
            for (Map.Entry<String, BeanWrapper> entry : factoryBeanInstanceCache.entrySet()) {
                for (Annotation annotation : entry.getValue().getWrapperClass().getAnnotations()) {
                    if (annotation.annotationType() == classZ) {
                        resultMap.put(entry.getKey(), entry.getValue());
                    }
                }
            }
        }
        return resultMap;
    }

}
